/*
 * Copyright (c) 2021-2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "user/user_manager.h"
#include "bundle_mgr_client.h"

#include <sys/stat.h>

#ifdef USER_CRYPTO_MANAGER
#include "crypto/key_manager.h"
#endif
#include "istorage_daemon.h"
#include "storage_service_constant.h"
#include "storage_service_errno.h"
#include "storage_service_log.h"
#include "user/mount_constant.h"
#include "utils/storage_radar.h"
#include "utils/string_utils.h"

using namespace std;
using namespace OHOS::StorageService;
namespace OHOS {
namespace StorageDaemon {
UserManager::UserManager()
    : rootDirVec_{{"/data/app/%s/%d", MODE_0711, OID_ROOT, OID_ROOT},
                  {"/data/service/%s/%d", MODE_0711, OID_ROOT, OID_ROOT},
                  {"/data/chipset/%s/%d", MODE_0711, OID_ROOT, OID_ROOT}},
      el3DirEl4DirEl5DirVec_{{"/data/app/%s/%d", MODE_0711, OID_ROOT, OID_ROOT},
                     {"/data/service/%s/%d", MODE_0711, OID_ROOT, OID_ROOT}},
      subDirVec_{{"/data/app/%s/%d/base", MODE_0711, OID_ROOT, OID_ROOT},
                 {"/data/app/%s/%d/database", MODE_0711, OID_ROOT, OID_ROOT}},
      el2DirVec_{{"/data/service/el2/%d/backup", MODE_02771, OID_BACKUP, OID_BACKUP},
                 {"/data/service/el2/%d/backup/backup_sa", MODE_0711, OID_BACKUP, OID_BACKUP},
                 {"/data/service/el2/%d/backup/bundles", MODE_0711, OID_BACKUP, OID_BACKUP},
                 {"/data/app/el2/%d/log", MODE_0711, OID_ROOT, OID_ROOT},
                 {"/data/app/el2/%d/sharefiles", MODE_0711, OID_ROOT, OID_ROOT}},
      el1DirVec_{{"/data/service/el1/%d/distributeddata", MODE_0711, OID_DDMS, OID_DDMS},
                 {"/data/service/el1/%d/backup", MODE_02771, OID_BACKUP, OID_BACKUP},
                 {"/data/service/el1/%d/backup/bundles", MODE_0711, OID_BACKUP, OID_BACKUP},
                 {"/data/service/el1/%d/utdtypes", MODE_0711, OID_FOUNDATION, OID_FOUNDATION},
                 {"/data/app/el1/%d/aot_compiler", MODE_0711, OID_ROOT, OID_ROOT},
                 {"/data/app/el1/%d/aot_compiler/ark_profile", MODE_0711, OID_ROOT, OID_ROOT},
                 {"/data/app/el1/%d/shader_cache", MODE_0711, OID_ROOT, OID_ROOT}}
{
}

UserManager &UserManager::GetInstance()
{
    static UserManager instance_;
    return instance_;
}

int32_t UserManager::StartUser(int32_t userId)
{
    LOGI("start user %{public}d", userId);
    std::lock_guard<std::mutex> lock(mutex_);
    int32_t err = CheckUserIdRange(userId);
    if (err != E_OK) {
        LOGE("UserManager::StartUser userId %{public}d out of range", userId);
        return err;
    }
    return MountManager::GetInstance().MountByUser(userId);
}

int32_t UserManager::StopUser(int32_t userId)
{
    LOGI("stop user %{public}d", userId);
    std::lock_guard<std::mutex> lock(mutex_);
    int32_t err = CheckUserIdRange(userId);
    if (err != E_OK) {
        LOGE("UserManager::StopUser userId %{public}d out of range", userId);
        return err;
    }
    return MountManager::GetInstance().UmountByUser(userId);
}

int32_t UserManager::PrepareUserDirs(int32_t userId, uint32_t flags)
{
    LOGI("prepare user dirs for %{public}d, flags %{public}u", userId, flags);
    std::lock_guard<std::mutex> lock(mutex_);
    int32_t err = CheckCrypto(userId, flags);
    if (err != E_OK) {
        return err;
    }
    if (flags & IStorageDaemonEnum::CRYPTO_FLAG_EL2) {
        err = MountManager::GetInstance().PrepareHmdfsDirs(userId);
        if (err != E_OK) {
            LOGE("Prepare hmdfs dir error");
            return err;
        }
        err = MountManager::GetInstance().PrepareFileManagerDirs(userId);
        if (err != E_OK) {
            LOGE("Prepare fileManager dir error");
            return err;
        }
    }

    return E_OK;
}

int32_t UserManager::DestroyUserDirs(int32_t userId, uint32_t flags)
{
    LOGI("destroy user dirs for %{public}d, flags %{public}u", userId, flags);
    std::lock_guard<std::mutex> lock(mutex_);
    int32_t err = CheckUserIdRange(userId);
    if (err != E_OK) {
        LOGE("UserManager::DestroyUserDirs userId %{public}d out of range", userId);
        return err;
    }
    int32_t ret = E_OK;
    if (flags & IStorageDaemonEnum::CRYPTO_FLAG_EL1) {
        err = DestroyDirsFromIdAndLevel(userId, EL1);
        ret = (err != E_OK) ? err : ret;

        err = DestroyEl1BundleDir(userId);
        ret = (err != E_OK) ? err : ret;
    }
    if (flags & IStorageDaemonEnum::CRYPTO_FLAG_EL2) {
        err = DestroyDirsFromIdAndLevel(userId, EL2);
        ret = (err != E_OK) ? err : ret;

        err = MountManager::GetInstance().DestroyFileManagerDirs(userId);
        ret = (err != E_OK) ? err : ret;

        err = MountManager::GetInstance().DestroyHmdfsDirs(userId);
        ret = (err != E_OK) ? err : ret;

        err = MountManager::GetInstance().DestroySystemServiceDirs(userId);
        ret = (err != E_OK) ? err : ret;

        err = DestroyEl2BackupDir(userId);
        ret = (err != E_OK) ? err : ret;

        err = DestroyEl1Dir(userId);
        ret = (err != E_OK) ? err : ret;
    }
    if (flags & IStorageDaemonEnum::CRYPTO_FLAG_EL3) {
        err = DestroyDirsFromIdAndLevel(userId, EL3);
        ret = (err != E_OK) ? err : ret;
    }
    if (flags & IStorageDaemonEnum::CRYPTO_FLAG_EL4) {
        err = DestroyDirsFromIdAndLevel(userId, EL4);
        ret = (err != E_OK) ? err : ret;
    }
    if (flags & IStorageDaemonEnum::CRYPTO_FLAG_EL5) {
        err = DestroyDirsFromIdAndLevel(userId, EL5);
        ret = (err != E_OK) ? err : ret;
    }

    return ret;
}

inline bool PrepareDirsFromVec(int32_t userId, const std::string &level, const std::vector<DirInfo> &vec)
{
    for (const DirInfo &dir : vec) {
        if (!PrepareDir(StringPrintf(dir.path.c_str(), level.c_str(), userId), dir.mode, dir.uid, dir.gid)) {
            return false;
        }
    }

    return true;
}

inline bool DestroyDirsFromVec(int32_t userId, const std::string &level, const std::vector<DirInfo> &vec)
{
    bool err = true;

    for (const DirInfo &dir : vec) {
        if (IsEndWith(dir.path.c_str(), "%d")) {
            err = RmDirRecurse(StringPrintf(dir.path.c_str(), level.c_str(), userId));
        }
    }

    return err;
}

int32_t UserManager::PrepareDirsFromIdAndLevel(int32_t userId, const std::string &level)
{
    std::vector<FileList> list;
    if (level != EL3 && level != EL4 && level != EL5) {
        if (!PrepareDirsFromVec(userId, level, rootDirVec_)) {
            LOGE("failed to prepare %{public}s root dirs for userid %{public}d", level.c_str(), userId);
            if (level != EL1) {
                return E_PREPARE_DIR;
            }
        }
        // set policy here
        for (auto item : rootDirVec_) {
            FileList temp;
            temp.userId = static_cast<uint32_t>(userId);
            temp.path = StringPrintf(item.path.c_str(), level.c_str(), userId);
            list.push_back(temp);
        }
    } else {
        if (!PrepareDirsFromVec(userId, level, el3DirEl4DirEl5DirVec_)) {
            LOGE("failed to prepare %{public}s root dirs for userid %{public}d", level.c_str(), userId);
            return E_PREPARE_DIR;
        }
        // set policy here
        for (auto item : el3DirEl4DirEl5DirVec_) {
            FileList temp;
            temp.userId = static_cast<uint32_t>(userId);
            temp.path = StringPrintf(item.path.c_str(), level.c_str(), userId);
            list.push_back(temp);
        }
    }
    int ret = SetElDirFscryptPolicy(userId, level, list);
    if (ret != E_OK) {
        LOGE("Set el poilcy failed");
        return ret;
    }

    if (!PrepareDirsFromVec(userId, level, subDirVec_)) {
        LOGE("failed to prepare %{public}s sub dirs for userid %{public}d", level.c_str(), userId);
        return E_PREPARE_DIR;
    }

    return E_OK;
}

void UserManager::CheckDirsFromVec(int32_t userId)
{
    std::vector<std::string> levels = { EL1, EL2, EL3, EL4, EL5 };

    for (const std::string &level : levels) {
        if (!PrepareDirsFromVec(userId, level, subDirVec_)) {
            LOGE("failed to prepare %{public}s sub dirs for userid %{public}d", level.c_str(), userId);
        }
    }
}

int32_t UserManager::CreateUserDir(const std::string &path, mode_t mode, uid_t uid, gid_t gid)
{
    LOGI("CreateUserDir path: %{public}s, %{public}d, %{public}d, %{public}d", path.c_str(), mode, uid, gid);
    std::string prefix = "/data/virt_service/rgm_hmos/anco_hmos_data/";
    if (path.compare(0, prefix.size(), prefix) != 0) {
        LOGE("The path: %{public}s is invalid", path.c_str());
        return E_PARAMS_INVALID;
    }

    auto ret = PrepareDirSimple(path, mode, uid, gid);
    if (ret != E_OK) {
        LOGE("Failed to prepareDir %{public}s, ret: %{public}d", path.c_str(), ret);
    } else {
        LOGI("CreateUserDir end. ret: %{public}d", ret);
    }
    std::string extraData = "path=" + path + ", mode=" + std::to_string(mode) +
        ", uid=" + std::to_string(uid) + ", gid=" + std::to_string(gid);
    StorageRadar::ReportUserManager("CreateUserDir", 0, ret, extraData);
    return ret;
}

int32_t UserManager::DeleteUserDir(const std::string &path)
{
    LOGE("DeleteUserDir path: %{public}s", path.c_str());
    std::string prefix = "/data/virt_service/rgm_hmos/anco_hmos_data/";
    if (path.compare(0, prefix.size(), prefix) != 0) {
        LOGE("The path: %{public}s is invalid", path.c_str());
        return E_PARAMS_INVALID;
    }

    struct stat pathStat;
    if (TEMP_FAILURE_RETRY(lstat(path.c_str(), &pathStat)) != 0) {
        if (errno == ENOENT) {
            LOGE("The path: %{public}s does not exist.", path.c_str());
            return E_DELETE_USER_DIR_NOEXIST;
        }
        LOGE("The path: %{public}s cannot be accessed. errno: %{public}d", path.c_str(), errno);
        return E_DELETE_USER_DIR_LSTAT;
    }

    if (!S_ISDIR(pathStat.st_mode)) {
        LOGE("The path: %{public}s is not a directory.", path.c_str());
        return E_DELETE_USER_DIR_NOTDIR;
    }

    bool isPathEmpty = true;
    auto ret = DestroyDir(path, isPathEmpty);

    std::string extraData = "path=" + path + ", isPathEmpty=" + std::to_string(isPathEmpty);
    LOGE("DeleteUserDir end, ret=%{public}d, %{public}s", ret, extraData.c_str());
    StorageRadar::ReportUserManager("DeleteUserDir", 0, ret, extraData);
    return ret;
}

int32_t UserManager::DestroyDirsFromIdAndLevel(int32_t userId, const std::string &level)
{
    if (level != EL3 && level != EL4 && level != EL5) {
        if (!DestroyDirsFromVec(userId, level, rootDirVec_)) {
            LOGE("failed to destroy %{public}s dirs for userid %{public}d", level.c_str(), userId);
            return E_DESTROY_DIR;
        }
    } else {
        if (!DestroyDirsFromVec(userId, level, el3DirEl4DirEl5DirVec_)) {
            LOGE("failed to destroy %{public}s dirs for userid %{public}d", level.c_str(), userId);
            return E_DESTROY_DIR;
        }
    }
    return E_OK;
}

int32_t UserManager::PrepareEl1BundleDir(int32_t userId)
{
    if (!PrepareDir(StringPrintf(bundle_, userId), MODE_0711, OID_ROOT, OID_ROOT)) {
        return E_PREPARE_DIR;
    }

    // set policy here
    std::vector<FileList> list;
    FileList temp;
    temp.userId = static_cast<uint32_t>(userId);
    temp.path = StringPrintf(bundle_, userId);
    list.push_back(temp);
    int ret = SetElDirFscryptPolicy(userId, EL1, list);
    if (ret != E_OK) {
        LOGE("Set el1 poilcy failed");
        return ret;
    }

    return E_OK;
}

int32_t UserManager::DestroyEl1BundleDir(int32_t userId)
{
    if (!RmDirRecurse(StringPrintf(bundle_, userId))) {
        return E_DESTROY_DIR;
    }

    return E_OK;
}

int32_t UserManager::SetElDirFscryptPolicy(int32_t userId, const std::string &level,
                                           const std::vector<FileList> &list)
{
#ifdef USER_CRYPTO_MANAGER
    if (EL_DIR_MAP.find(level) == EL_DIR_MAP.end()) {
        LOGE("el type error");
        return E_SET_POLICY;
    }
    if (KeyManager::GetInstance().SetDirectoryElPolicy(userId, EL_DIR_MAP[level], list)) {
        LOGE("Set user dir el1 policy error");
        return E_SET_POLICY;
    }
#endif

    return E_OK;
}

int32_t UserManager::PrepareEl2BackupDir(int32_t userId)
{
    for (const DirInfo &dir : el2DirVec_) {
        if (!PrepareDir(StringPrintf(dir.path.c_str(), userId), dir.mode, dir.uid, dir.gid)) {
            return E_PREPARE_DIR;
        }
    }

    return E_OK;
}

int32_t UserManager::DestroyEl2BackupDir(int32_t userId)
{
    for (const DirInfo &dir : el2DirVec_) {
        if (!RmDirRecurse(StringPrintf(dir.path.c_str(), userId))) {
            return E_DESTROY_DIR;
        }
    }

    return E_OK;
}

int32_t UserManager::PrepareEl1Dir(int32_t userId)
{
    for (const DirInfo &dir : el1DirVec_) {
        if (!PrepareDir(StringPrintf(dir.path.c_str(), userId), dir.mode, dir.uid, dir.gid)) {
            return E_PREPARE_DIR;
        }
    }

    return E_OK;
}

int32_t UserManager::DestroyEl1Dir(int32_t userId)
{
    for (const DirInfo &dir : el1DirVec_) {
        if (!RmDirRecurse(StringPrintf(dir.path.c_str(), userId))) {
            return E_DESTROY_DIR;
        }
    }

    return E_OK;
}

void UserManager::CreateElxBundleDataDir(uint32_t userId, uint8_t elx)
{
    LOGI("CreateElxBundleDataDir start: userId %{public}u, elx is %{public}d", userId, elx);
    if (elx == EL1_KEY) {
        LOGW("CreateElxBundleDataDir pass: userId %{public}u, elx is %{public}d", userId, elx);
        return;
    }
    OHOS::AppExecFwk::BundleMgrClient client;
    auto ret = client.CreateBundleDataDirWithEl(userId, static_cast<OHOS::AppExecFwk::DataDirEl>(elx));
    LOGI("CreateElxBundleDataDir end: userId %{public}u, elx is %{public}d, ret %{public}d", userId, elx, ret);
    if (ret != E_OK) {
        StorageRadar::ReportBundleMgrResult("CreateElxBundleDataDir", ret, userId, std::to_string(elx));
    }
}

int32_t UserManager::CheckUserIdRange(int32_t userId)
{
    if ((userId < StorageService::START_USER_ID && userId != StorageService::ZERO_USER) ||
        userId > StorageService::MAX_USER_ID) {
        LOGE("UserManager: userId:%{public}d is out of range", userId);
        return E_USERID_RANGE;
    }
    return E_OK;
}

int32_t UserManager::CheckCrypto(int32_t userId, uint32_t flags)
{
    int32_t err = CheckUserIdRange(userId);
    if (err != E_OK) {
        LOGE("UserManager::PrepareUserDirs userId %{public}d out of range", userId);
        return err;
    }
    if (flags & IStorageDaemonEnum::CRYPTO_FLAG_EL1) {
        err = PrepareDirsFromIdAndLevel(userId, EL1);
        if (err != E_OK) {
            return err;
        }
        err = PrepareEl1BundleDir(userId);
        if (err != E_OK) {
            return err;
        }
        int32_t errorCode = PrepareEl1Dir(userId);
        if (errorCode != E_OK) {
            LOGW("Prepare el1 dir fail, %{public}d.", errorCode);
        }
    }
    if (flags & IStorageDaemonEnum::CRYPTO_FLAG_EL2) {
        err = PrepareDirsFromIdAndLevel(userId, EL2);
        if (err != E_OK) {
            return err;
        }
        err = PrepareEl2BackupDir(userId);
        if (err != E_OK) {
            return err;
        }
    }
    if (flags & IStorageDaemonEnum::CRYPTO_FLAG_EL3) {
        err = PrepareDirsFromIdAndLevel(userId, EL3);
        if (err != E_OK) {
            return err;
        }
    }
    if (flags & IStorageDaemonEnum::CRYPTO_FLAG_EL4) {
        err = PrepareDirsFromIdAndLevel(userId, EL4);
        if (err != E_OK) {
            return err;
        }
    }
    if (flags & IStorageDaemonEnum::CRYPTO_FLAG_EL5) {
        err = PrepareDirsFromIdAndLevel(userId, EL5);
        if (err != E_OK) {
            return err;
        }
    }
    return E_OK;
}
} // namespace StorageDaemon
} // namespace OHOS
